1use crate::copp::CoppObjective;
32use crate::copp::constraints::Constraints;
33use crate::diag::{CoppError, check_boundary_state_copp3_valid, check_s_interval_valid};
34use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
35use itertools::izip;
36
37const DEFAULT_A_LINEARIZATION_FLOOR: f64 = 1E-10;
38const DEFAULT_NUM_STATIONARY_MAX: (usize, usize) = (1, 1);
39
40#[inline(always)]
41fn determine_num_stationary_side(a: f64, b: f64, num_stationary_max: usize) -> usize {
42 if a.abs() < f64::EPSILON && b.abs() < f64::EPSILON {
43 num_stationary_max
44 } else {
45 0
46 }
47}
48
49#[inline(always)]
50fn determine_num_stationary_pair(
51 a_boundary: (f64, f64),
52 b_boundary: (f64, f64),
53 num_stationary_max: (usize, usize),
54) -> (usize, usize) {
55 (
56 determine_num_stationary_side(a_boundary.0, b_boundary.0, num_stationary_max.0),
57 determine_num_stationary_side(a_boundary.1, b_boundary.1, num_stationary_max.1),
58 )
59}
60
61pub struct Topp3Problem<'a> {
89 pub(crate) constraints: &'a Constraints,
90 pub(crate) idx_s_start: usize,
91 pub(crate) a_linearization: &'a [f64],
92 pub(crate) a_boundary: (f64, f64),
93 pub(crate) b_boundary: (f64, f64),
94 pub(crate) num_stationary: (usize, usize),
97}
98
99pub struct Topp3ProblemBuilder<'a> {
105 pub constraints: &'a mut Constraints,
107 pub idx_s_start: usize,
109 pub a_linearization: &'a [f64],
111 pub a_boundary: (f64, f64),
113 pub b_boundary: (f64, f64),
115 pub num_stationary_max: (usize, usize),
117 pub a_linearization_floor: f64,
128}
129
130impl<'a> Topp3ProblemBuilder<'a> {
131 pub fn new<M: RobotBasic>(
137 robot: &'a mut Robot<M>,
138 idx_s_start: usize,
139 a_linearization: &'a [f64],
140 a_boundary: (f64, f64),
141 b_boundary: (f64, f64),
142 ) -> Self {
143 Self {
144 constraints: &mut robot.constraints,
145 idx_s_start,
146 a_linearization,
147 a_boundary,
148 b_boundary,
149 num_stationary_max: DEFAULT_NUM_STATIONARY_MAX,
150 a_linearization_floor: DEFAULT_A_LINEARIZATION_FLOOR,
151 }
152 }
153
154 pub fn with_constraint(
160 constraints: &'a mut Constraints,
161 idx_s_start: usize,
162 a_linearization: &'a [f64],
163 a_boundary: (f64, f64),
164 b_boundary: (f64, f64),
165 ) -> Self {
166 Self {
167 constraints,
168 idx_s_start,
169 a_linearization,
170 a_boundary,
171 b_boundary,
172 num_stationary_max: DEFAULT_NUM_STATIONARY_MAX,
173 a_linearization_floor: DEFAULT_A_LINEARIZATION_FLOOR,
174 }
175 }
176
177 #[inline]
181 pub fn with_num_stationary_max(mut self, num_stationary_max: usize) -> Self {
182 self.num_stationary_max = (num_stationary_max, num_stationary_max);
183 self
184 }
185
186 #[inline]
190 pub fn with_num_stationary_max_pair(mut self, num_stationary_max: (usize, usize)) -> Self {
191 self.num_stationary_max = num_stationary_max;
192 self
193 }
194
195 #[inline]
197 pub fn with_a_linearization_floor(mut self, floor: f64) -> Self {
198 self.a_linearization_floor = floor;
199 self
200 }
201
202 pub fn build_with_linearization(self) -> Result<Topp3Problem<'a>, CoppError> {
206 check_boundary_state_copp3_valid(self.a_boundary, self.b_boundary)?;
207 if self.a_linearization.is_empty() {
208 return Err(CoppError::InvalidInput(
209 "Topp3ProblemBuilder::build_with_linearization".into(),
210 "a_linearization cannot be empty".into(),
211 ));
212 }
213 let idx_s_final = self.idx_s_start + self.a_linearization.len() - 1;
214 check_s_interval_valid(
215 "Topp3ProblemBuilder::build_with_linearization",
216 self.idx_s_start,
217 idx_s_final,
218 )?;
219 if self.a_linearization_floor <= 0.0 {
220 return Err(CoppError::InvalidInput(
221 "Topp3ProblemBuilder::build_with_linearization".into(),
222 format!(
223 "a_linearization_floor must be positive, got {}",
224 self.a_linearization_floor
225 ),
226 ));
227 }
228
229 self.constraints
230 .linearize_constraint_3order_with_floor(
231 self.a_linearization,
232 self.idx_s_start,
233 self.a_linearization_floor,
234 )
235 .map_err(|e| {
236 CoppError::InvalidInput(
237 "Topp3ProblemBuilder::build_with_linearization".into(),
238 format!("linearize_constraint_3order failed: {e}"),
239 )
240 })?;
241
242 let num_stationary = determine_num_stationary_pair(
243 self.a_boundary,
244 self.b_boundary,
245 self.num_stationary_max,
246 );
247
248 Ok(Topp3Problem {
249 constraints: &*self.constraints,
250 idx_s_start: self.idx_s_start,
251 a_linearization: self.a_linearization,
252 a_boundary: self.a_boundary,
253 b_boundary: self.b_boundary,
254 num_stationary,
255 })
256 }
257}
258
259pub struct Copp3Problem<'a, M: RobotTorque> {
269 pub(crate) robot: &'a mut Robot<M>,
270 pub(crate) objectives: &'a [CoppObjective<'a>],
271 pub(crate) idx_s_start: usize,
272 pub(crate) a_linearization: &'a [f64],
273 pub(crate) a_boundary: (f64, f64),
274 pub(crate) b_boundary: (f64, f64),
275 pub(crate) num_stationary: (usize, usize),
278}
279
280pub struct Copp3ProblemBuilder<'a, M: RobotTorque> {
282 pub robot: &'a mut Robot<M>,
284 pub objectives: &'a [CoppObjective<'a>],
286 pub idx_s_start: usize,
288 pub a_linearization: &'a [f64],
290 pub a_boundary: (f64, f64),
292 pub b_boundary: (f64, f64),
294 pub num_stationary_max: (usize, usize),
296}
297
298impl<'a, M: RobotTorque> Copp3ProblemBuilder<'a, M> {
299 pub fn new(
304 robot: &'a mut Robot<M>,
305 objectives: &'a [CoppObjective<'a>],
306 idx_s_start: usize,
307 a_linearization: &'a [f64],
308 a_boundary: (f64, f64),
309 b_boundary: (f64, f64),
310 ) -> Self {
311 Self {
312 robot,
313 objectives,
314 idx_s_start,
315 a_linearization,
316 a_boundary,
317 b_boundary,
318 num_stationary_max: DEFAULT_NUM_STATIONARY_MAX,
319 }
320 }
321
322 #[inline]
326 pub fn with_num_stationary_max(mut self, num_stationary_max: usize) -> Self {
327 self.num_stationary_max = (num_stationary_max, num_stationary_max);
328 self
329 }
330
331 #[inline]
335 pub fn with_num_stationary_max_pair(mut self, num_stationary_max: (usize, usize)) -> Self {
336 self.num_stationary_max = num_stationary_max;
337 self
338 }
339
340 pub fn build_with_linearization(self) -> Result<Copp3Problem<'a, M>, CoppError> {
342 check_boundary_state_copp3_valid(self.a_boundary, self.b_boundary)?;
343 if self.a_linearization.is_empty() {
344 return Err(CoppError::InvalidInput(
345 "Copp3ProblemBuilder::build_with_linearization".into(),
346 "a_linearization cannot be empty".into(),
347 ));
348 }
349 let idx_s_final = self.idx_s_start + self.a_linearization.len() - 1;
350 check_s_interval_valid(
351 "Copp3ProblemBuilder::build_with_linearization",
352 self.idx_s_start,
353 idx_s_final,
354 )?;
355 self.robot
356 .constraints
357 .check_s_in_bounds(self.idx_s_start, self.a_linearization.len())?;
358
359 self.robot
360 .constraints
361 .linearize_constraint_3order_with_floor(
362 self.a_linearization,
363 self.idx_s_start,
364 DEFAULT_A_LINEARIZATION_FLOOR,
365 )
366 .map_err(|e| {
367 CoppError::InvalidInput(
368 "Copp3ProblemBuilder::build_with_linearization".into(),
369 format!("linearize_constraint_3order failed: {e}"),
370 )
371 })?;
372
373 let num_stationary = determine_num_stationary_pair(
374 self.a_boundary,
375 self.b_boundary,
376 self.num_stationary_max,
377 );
378
379 Ok(Copp3Problem {
380 robot: self.robot,
381 objectives: self.objectives,
382 idx_s_start: self.idx_s_start,
383 a_linearization: self.a_linearization,
384 a_boundary: self.a_boundary,
385 b_boundary: self.b_boundary,
386 num_stationary,
387 })
388 }
389}
390
391impl<'a, M: RobotTorque> Copp3Problem<'a, M> {
392 #[inline]
394 pub fn set_a_linearization(&mut self, a_linearization: &'a [f64]) {
395 self.a_linearization = a_linearization;
396 }
397
398 #[inline]
400 pub fn set_a_linear(&mut self, a_linearization: &'a [f64]) {
401 self.set_a_linearization(a_linearization);
402 }
403
404 #[inline]
406 pub fn set_objective(&mut self, objective: &'a [CoppObjective<'a>]) {
407 self.objectives = objective;
408 }
409
410 pub fn as_topp3_problem(&self) -> Topp3Problem<'_> {
412 Topp3Problem {
413 constraints: &self.robot.constraints,
414 idx_s_start: self.idx_s_start,
415 a_linearization: self.a_linearization,
416 a_boundary: self.a_boundary,
417 b_boundary: self.b_boundary,
418 num_stationary: self.num_stationary,
419 }
420 }
421}
422
423pub(crate) fn get_weight_a_topp3(s: &[f64], num_stationary: (usize, usize)) -> Vec<f64> {
426 let n = s.len() - 1;
427 let mut weight_a = vec![0.0; s.len()];
428 if num_stationary.0 > 0 {
429 weight_a[num_stationary.0] =
430 0.5 * (5.0 * s[num_stationary.0] + s[num_stationary.0 + 1] - 6.0 * s[0]);
431 }
432 if num_stationary.1 > 0 {
433 weight_a[n - num_stationary.1] =
434 0.5 * (6.0 * s[n] - 5.0 * s[n - num_stationary.1] - s[n - num_stationary.1 - 1]);
435 }
436 weight_a
437 .iter_mut()
438 .skip(1)
439 .zip(s.windows(3))
440 .skip(num_stationary.0)
441 .take(n - num_stationary.0 - num_stationary.1 - 1)
442 .for_each(|(w_a, s_slice)| {
443 *w_a = 0.5 * (s_slice[2] - s_slice[0]);
444 });
445
446 weight_a
447}
448
449pub(crate) fn get_weight_a_copp3(s: &[f64], num_stationary: (usize, usize)) -> Vec<f64> {
452 let n = s.len() - 1;
453 let mut weight_a = vec![0.0; s.len()];
454 if num_stationary.0 > 0 {
455 let s_n1 = s[num_stationary.0];
456 weight_a[num_stationary.0] = 0.5 * (s[num_stationary.0 + 1] - s_n1);
457 weight_a[0] = 3.0 * (s_n1 - s[0]);
458 }
459 if num_stationary.1 > 0 {
460 let s_n2 = s[n - num_stationary.1];
461 weight_a[n - num_stationary.1] = 0.5 * (s_n2 - s[n - num_stationary.1 - 1]);
462 weight_a[n] = 3.0 * (s[n] - s_n2);
463 }
464 weight_a
465 .iter_mut()
466 .skip(1)
467 .zip(s.windows(3))
468 .skip(num_stationary.0)
469 .take(n - num_stationary.0 - num_stationary.1 - 1)
470 .for_each(|(w_a, s_slice)| {
471 *w_a = 0.5 * (s_slice[2] - s_slice[0]);
472 });
473
474 weight_a
475}
476
477pub(crate) fn set_ab_stationary_topp3<const START: bool>(
479 s: &[f64],
480 a: &mut [f64],
481 b: &mut [f64],
482 a_stationary: f64,
483 num_stationary: usize,
484) {
485 if num_stationary == 0 {
486 return;
487 }
488 if START {
489 let s_start = s[0];
490 let ds_start = s[num_stationary] - s_start;
491 *a.first_mut().unwrap() = 0.0;
492 *b.first_mut().unwrap() = 0.0;
493 a[num_stationary] = a_stationary;
494 b[num_stationary] = a_stationary / (1.5 * ds_start);
495 if num_stationary > 1 {
496 for (a_k, b_k, &s_k) in izip!(a.iter_mut(), b.iter_mut(), s.iter())
497 .skip(1)
498 .take(num_stationary - 1)
499 {
500 let dsk_start = s_k - s_start;
501 let mut alpha = dsk_start / ds_start;
502 alpha *= alpha.cbrt();
503 *a_k = a_stationary * alpha;
504 *b_k = *a_k / (1.5 * dsk_start);
505 }
506 }
507 } else {
508 let &s_final = s.last().unwrap();
509 let n = s.len() - 1;
510 let ds_final = s[n - num_stationary] - s_final;
511 *a.last_mut().unwrap() = 0.0;
512 *b.last_mut().unwrap() = 0.0;
513 a[a.len() - 1 - num_stationary] = a_stationary;
514 b[b.len() - 1 - num_stationary] = a_stationary / (1.5 * ds_final);
515 if num_stationary > 1 {
516 for (a_k, b_k, &s_k) in izip!(a.iter_mut().rev(), b.iter_mut().rev(), s.iter().rev())
517 .skip(1)
518 .take(num_stationary - 1)
519 {
520 let dsk_final = s_k - s_final;
521 let mut alpha = dsk_final / ds_final;
522 alpha *= alpha.cbrt();
523 *a_k = a_stationary * alpha;
524 *b_k = *a_k / (1.5 * dsk_final);
525 }
526 }
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::determine_num_stationary_pair;
533
534 #[test]
535 fn test_determine_num_stationary_pair_respects_boundary_state() {
536 let pair = determine_num_stationary_pair((0.0, 0.0), (0.0, 0.0), (2, 3));
537 assert_eq!(pair, (2, 3));
538
539 let pair = determine_num_stationary_pair((1.0, 0.0), (0.0, 0.0), (2, 3));
540 assert_eq!(pair, (0, 3));
541
542 let pair = determine_num_stationary_pair((0.0, 1.0), (0.0, 1.0), (2, 3));
543 assert_eq!(pair, (2, 0));
544 }
545}